﻿# CoffeeScript
if not window.Moshu
    window.Moshu = new Object();
window.Moshu.Canvas2D = new Object();

class window.Moshu.Canvas2D.Vertex extends Array
    constructor : (x,y) ->
        super
        @x = x
        @y = y
        @push(x)
        @push(y)
    set : (x,y) ->
        @x = x
        @y = y
        this[0] = x
        this[1] = y
    copy : (vertex) ->        
        @set(vertex[0],vertex[1]) 

        # Vector class - used for SAT algorithm
# Supports a few operations on vectors needed for SAT
class window.Moshu.Canvas2D.Vector
    constructor : (@vector) ->        
    substract : (vector) ->
        if not @vector or not @vector.length 
            null
        else
            if not vector or not vector.length or not vector.length is @vector.length
                null
            else
                self = this
                for x,i in @vector
                    @vector[i] -= vector[i]                
    invert : () ->  
        for v,i in @vector
            if i < @vector.length/2
                #invert with the complement in array
                aux = @vector[i]
                @vector[i] = @vector[@vector.length-1-i]
                @vector[@vector.length-1-i] = aux        
    perp : ()->        
        @invert()
        @vector[0] = - @vector[0]
    dotProduct : (vector) ->
        if not @vector or not @vector.length 
            0
        else
            if not vector or not vector.length or not vector.length is @vector.length
                0
            else
                dot = 0
                self = this
                for v,i in @vector
                    dot += @vector[i] * vector[i]                
                dot

class window.Moshu.Canvas2D.Matrix extends Array
    constructor : () ->
        for i in [0..3]
            @push(0)
            
class window.Moshu.Canvas2D.Collision
    constructor : (@collider, @collidee, @collisionVector) ->
    
    # move shapes out of collision
    recover : () ->
        
            
class window.Moshu.Canvas2D.Rotation
    constructor : (centerx, centery, angle) ->
        @center = new Moshu.Canvas2D.Vertex(centerx,centery)
        @matrix = new Moshu.Canvas2D.Matrix()
        cosAngle = Math.cos(angle)
        sinAngle = Math.sin(angle)
        @matrix[0] = cosAngle
        @matrix[1] = -sinAngle
        @matrix[2] = sinAngle
        @matrix[3] = cosAngle
        @offset = new Moshu.Canvas2D.Vertex(centerx,centery)
    apply : (vertex) ->

class window.Moshu.Canvas2D.PhysicalObject extends Moshu.Observable
    constructor : () ->
        super
        #no mass
        @mass = 0
        #no elasticity
        @elasticity = 0
        #no friction with air
        @mediumFrictionCoef = 0
        #no friction with other objects
        @objectFrictionCoef = 0
        # no speed
        @velocity = 
            dx : 0
            dy : 0
        # no acceleration
        @acceleration = 
            ax : 0
            ay : 0
        # is fixed - specifies whether the object is fixed (does not move)
        @fixed = true
        
    getMomentum : () ->
        energy = 
            ox : 0
            oy : 0
        if not @fixed            
            energy = 
                ex : @mass * @velocity.dx
                ey : @mass * @velocity.dy
        energy
        
    setMomentum : (momentum) ->
        if @fixed
            @velocity.dx = 0
            @velocity.dy = 0
            
        @velocity.dx = momentum.ox / @mass
        @velocity.dy = momentum.oy / @mass
        
        
class window.Moshu.Canvas2D.Shape extends Moshu.Canvas2D.PhysicalObject
    constructor : () ->
        super()
        #additional properties for shape objects
        @id = new Date().getTime()
        @zIndex = 1;
                
        #the scene this shape belongs to
        @scene = null                
        #specifies whether the shape will be drawn on the screen
        @visible = true        
        #specifies that this is a shape
        @isShape = true
        
        @fixed = false
        
        #stroke attribute
        @stroke = 
            enabled : true
            width : 1
            color : "#000"
          # fill enable and color
          @fill = 
            enabled : false
            color : "#fff"        
                        
    # sets the filling color if neccesary
    setFillStyles : () ->
        ctx = @getContext()
        if @fill.enabled			
            ctx.fillStyle = @fill.color
        else
            ctx.fillStyle = null
            
    # sets the border colors to the canvas 			
    setStrokeStyles : () -> 
        ctx = @getContext()
        if @stroke.enabled
            ctx.lineWidth = @stroke.width
            ctx.strokeStyle = @stroke.color
        else 
            ctx.lineWidth = 0
            
    # sets fill and stroke styles
    setStyles : () ->
        @setStrokeStyles();
        @setFillStyles();
        true
      
    # executes the draw or fill depending on the current settings  
    drawCommand : () ->
        ctx = @getContext()
        if ctx
            if @stroke.enabled
                ctx.stroke();
            if @fill.enabled
                ctx.fill();
    getContext : ()->
        if @scene
            @scene.context
        else null
        
    timeUpdate : (dt)->
    draw : () ->
    
class window.Moshu.Canvas2D.Scene extends Moshu.Observable
    constructor: (@canvasId) ->
        super        
        @_shapes = []
        @canvas = document.getElementById(@canvasId);
        @context = @canvas.getContext("2d");
        window.Moshu.AnimationManager.bind(this, @timeUpdate)
        @collisionManager = new Moshu.Canvas2D.CollisionManager()
    # add a shape to the scene
    add : (shape) ->
        if shape and shape.isShape
            @_shapes.push(shape)
            shape.scene = this
            
    # remove a shape to the scene
    remove : (shape) ->
        for s,i in @_shapes
            if s is shape
                @_shapes.splice(i,1)
                shape.scene = null
                break
        shape
    # remove a shape to the scene
    removeById : (shapeId) ->
        for s,i in @_shapes
            if s.id is shapeId
                @_shapes.splice(i,1)
                break
        shape
    
    clear : () ->
        @_shapes = []
        @update()
        
    shapes : ()->
        @_shapes
            
    # redraw the scene  
    update : () ->
        # clear the scene
        @context.clearRect(0,0, @canvas.width, @canvas.height)
        # draw all shapes
        for s in @_shapes            
            # check if the shape is to be drawn
            if s.visible
                s.draw()
    
    invalidate : () ->         
        @update()
        
    timeUpdate : (dt)->
        for shape in @_shapes
            if shape and not shape.fixed            
                shape.timeUpdate(dt)
        @update()
        
    getBoundaryCollisions : (shape)->
        arr = []
        if shape and shape.boundary            
            for s,i in @_shapes
                if not (shape is s) and s.boundary()
                    b1 = shape.boundary()
                    b2 = s.boundary()
                    minAx = b1[0]
                    minAy = b1[1]
                    maxAx = b1[2]
                    maxAy = b1[3]
		
                    minBx = b2[0]
                    minBy = b2[1]
                    maxBx = b2[2]
                    maxBy = b2[3]
		
                    aLeftOfB = maxAx < minBx
                    aRightOfB = minAx > maxBx
                    aAboveB = minAy > maxBy
                    aBelowB = maxAy < minBy
                    if !( aLeftOfB || aRightOfB || aAboveB || aBelowB )
                        arr.push(s)
        arr        
    getCollisions : (shape) ->
        boundaryCollisions = @getBoundaryCollisions(shape)
        collisions = []
        for c in boundaryCollisions
            # we should check with SAT as well, before adding it
            # however for now we leave it with boundary collision
            col = @collisionManager.checkCollide(shape, c)
            if col            
                collisions.push(col)
        collisions
    

class window.Moshu.Canvas2D.Polygon extends Moshu.Canvas2D.Shape
    constructor : (vertices) ->
        super
        @_boundary = [0,0,0,0]
        @_vertices = []
        @center = new Moshu.Canvas2D.Vertex(0,0)
        @_position = new Moshu.Canvas2D.Vertex(0,0)        
        @vertices(vertices)  
        if @_vertices and @_vertices.length and @_vertices.length>0
            @_position.copy(@_vertices[0])
            
        # specifies whether we close the polygon when we draw it
        @isClosed = false
        @isPolygon = true
        @collisions = []
    # gets or sets the vertices of the shape        
    vertices : (vertices) ->
        if not vertices
            # return vertices
            @_vertices
        else
            # set vertices to a new value
            # check if vertices is an array
            if vertices and vertices.length
                @_vertices = []
                for elem in vertices
                    vertex = null;
                    if elem and elem.length and elem.length is 2
                        # elem is array with length 2                        
                        vertex = new Moshu.Canvas2D.Vertex(elem[0],elem[1]);
                    else
                        if elem and elem.x and elem.y
                            vertex = new Moshu.Canvas2D.Vertex(elem.x,elem.y);
                    if vertex                    
                        @_vertices.push(vertex)                    
                
                args = 
                    target : this
                    property : "vertices"
                    newValue : @_vertices
                # points have changed, we need to recompute the boundary and the center
                @updateBoundary()
                @updateCenter()
                
                # trigger property changed event                 
                @notify("property-changed",args)
                    
    position : (newPos) ->    
        if newPos
            if newPos.length and newPos.length is 2
                #we need to change the position
                newx = newPos[0]
                newy = newPos[1]
            else 
                if newPos.x and newPos.y
                    newx = newPos.x
                    newy = newPos.y
            if !(@_position.x is newx and @_position.y is newy)
                @_position.set(newx,newy)
                args = 
                    target : this
                    property : "position"
                    newValue : @_position
                    # trigger property changed event                 
                @notify("property-changed",args)
                # position has changed
                @updateBoundary()
                @updateCenter()
        @_position
        
    # move the polygon by dx and dy amount
    move : (dx,dy) ->
        for elem in @_vertices
            elem.set(elem.x+dx,elem.y+dy)
        p = @position()
        @position([p.x+dx, p.y+dy])
        
        if @scene
            @collisions = @scene.getCollisions(this)            
        args = 
            target : this
            x : p.x+dx
            y : p.y+dy
        @notify("moved", args)   
     
    # rotate the polygon by the angle with respect to a point called rotationCenter
    # if rotation center is not supplied, the object will rotate by its center of mass
    rotate : (angle,rotationCenter) ->
        if angle and angle != 0
            centerX = null
            centerY = null        
            if !rotationCenter
                centerX = @center.x
                centerY = @center.y
            else
                if rotationCenter and rotationCenter.length and rotationCenter.length is 2
                    centerX = rotationCenter[0]
                    centerY = rotationCenter[1]
                else 
                    if rotationCenter and rotationCenter.x and rotationCenter.y
                        centerX = rotationCenter.x
                        centerY = rotationCenter.y
            if centerX and centerY
                #rotation = new Moshu.Canvas2D.Rotation(centerX,centerY,angle)
                cosAngle = Math.cos(angle );
                sinAngle = Math.sin(angle );
                for vertex in @vertices()                    
                    dx = (vertex.x-centerX);
                    dy = (vertex.y-centerY);
                    vertex.set(centerX + dx*cosAngle-dy*sinAngle,centerY + dx*sinAngle+dy*cosAngle)                    
                #object vertexes have changed, we need to update boundary and center
                @updateBoundary()
                @updateCenter()                
                
                # trigger on-rotated event 
                args = 
                    target : this
                    rotatedBy : angle
                    centerX : centerX
                    centerY : centerY                    
                # trigger on rotated event                 
                @notify("on-rotated",args)           	                            
        
    # gets the boundary of the shape
    boundary : () ->
        @_boundary
        
    # updates the boundary of a shape, from the list of vertices
    updateBoundary : () ->
        if @_vertices and @_vertices.length
            minx = null
            maxx = null
            miny = null
            maxy = null
            for elem in @_vertices
                if elem[0]<minx or not minx 
                    minx = elem[0]
                if elem[0]>maxx or not maxx 
                    maxx = elem[0]
                if elem[1]<miny or not miny 
                    miny = elem[1]
                if elem[1]>maxy or not maxy 
                    maxy = elem[1]
            @_boundary = [minx,miny,maxx,maxy]
        else
            @_boundary = [0,0,0,0]
        @_boundary
        
    updateCenter : () ->
        if @_vertices and @_vertices.length and @_vertices.length>0
            totalx = 0
            totaly = 0
            for elem in @_vertices
                totalx += elem[0]
                totaly += elem[1]
            
            @center.set(totalx/@_vertices.length, totaly/@_vertices.length)            
        else 
            @center.set(0,0)
        @center
       
    # time updated - need to recompute position, if the object is self moving 
    timeUpdate : (dt)->        
        if !@fixed                
            dt = dt / 1000                  
            @velocity.dx += @acceleration.ax * dt;
            @velocity.dy += @acceleration.ay * dt;
        
            pos = @position()
            dx = @velocity.dx * dt
            dy = @velocity.dy * dt
                        
            # notify dx movement
            args = 
                target : this
                x : pos.x + dx
                y : pos.y
                dt : dt                
            cancel = !@notify("moving", args)
            if cancel
                dx = 0
            else
                @move(dx,0)
                # stop object if collided with something
                if @collisions.length>0                        
                    @notify("collision", this)                    
                    # for now we move back the object
                    @move(-dx,0)
                    
            # notify dy movement
            args = 
                target : this
                x : pos.x + dx
                y : pos.y + dy
                dt : dt
            cancel = !@notify("moving", args)
            if cancel
                dy = 0
            else                
                @move(0,dy)
                # stop object if collided with something                
                if @collisions.length>0                    
                    @notify("collision", this)                    
                    # for now we move back the object
                    @move(0,-dy)              
              
                
    # draws the polygon             
    draw : () ->
        ctx = @getContext()
        if ctx                                    
            @setStyles()
            ctx.beginPath()
            self = this
            vertices = @vertices()
            for vertex, i in vertices
                if i is 0
                    ctx.moveTo(vertex.x,vertex.y)
                else
                    ctx.lineTo(vertex.x,vertex.y)              
            if @isClosed
                if vertices.length > 2                    
                    ctx.lineTo(vertices[vertices.length-1].x,vertices[vertices.length-1].x)
                    
            ctx.closePath()                    
            @drawCommand()                    
                                         
# constructs a regular poligon    
class window.Moshu.Canvas2D.RegularPolygon extends Moshu.Canvas2D.Polygon
    constructor : (x,y,radius,rank)->    
        vertices = []        
        for i in [0..rank-1] by 1
            x = ( Math.sin( i / rank * 2 * Math.PI ) * radius ) + x
            y = ( Math.cos( i / rank * 2 * Math.PI ) * radius ) + y
            vertex = [x,y]
            vertices.push(vertex)
        super(vertices)
        
# classes for creating simple polygons    
class window.Moshu.Canvas2D.Line extends Moshu.Canvas2D.Polygon
    constructor : (x,y, width, height) ->
        super ([[x,y],[x+width, y+height]])
        @isClosed = false        

        
# constructs a Triangle
class window.Moshu.Canvas2D.Triangle extends Moshu.Canvas2D.Polygon
    constructor : (x1,y1,x2,y2,x3,y3)->
        super ([[x1,y1],[x2, y2],[x3, y3]])

# constructs a rectangle
class window.Moshu.Canvas2D.Rectangle extends Moshu.Canvas2D.Polygon
    constructor : (@x,@y,@width,@height)->
        super ([[@x,@y],[@x+@width, @y],[@x+@width, @y+@height],[@x, @y+@height]])
        
# constructs an image
class window.Moshu.Canvas2D.CanvasImage extends Moshu.Canvas2D.Rectangle
    constructor : (x,y,width,height, @url)->
        super(x,y,width,height)
        self = this
        @image = new Image()                
        @image.src = url
        @rotationAngle = 0
        @isCanvasImage = true
    rotate : (angle, rotationCenter) ->        
        super(angle)
        @rotationAngle += angle;
    move : (dx,dy) ->
        super(dx,dy)
        @x+=dx
        @y+=dy
    draw : ()->
        ctx = @getContext()
        if ctx            
            if @rotationAngle and !(@rotationAngle is 0)                
                #translate to rotation
                ctx.save()
                ctx.translate(@center.x, @center.y);
                ctx.rotate(@rotationAngle)
                ctx.drawImage(@image, @x-@center.x,@y-@center.y,@width,@height);
                ctx.restore()
            else
                ctx.drawImage(@image,@x,@y,@width,@height)
                
# PartialImage2D class - used for drawing images
class window.Moshu.Canvas2D.PartialCanvasImage extends Moshu.Canvas2D.CanvasImage
    constructor : (x,y,width,height, image) ->
        super(x,y,width,height,image)        
        
        # init clipping rect
        @clippingRectangle = 
            x : 0
            y : 0
            width : width
            height : height                               
    setClippingRectangle : (x,y,width,height) ->
        @clippingRectangle = 
                x : x
                y : y
                width : width
                height : height
                
    #draw the image
    draw : () ->
        ctx = @getContext()
        if ctx 
            ctx.drawImage(@image, @clippingRectangle.x, @clippingRectangle.y, @clippingRectangle.width, @clippingRectangle.height,@x, @y, @width, @height)
                            
# ImageSprite2D class - used for drawing images - sprites
# Very usefull for obtaining gif like animations
# This class was inherited from version 3. We should change it and improve it at some point.
class window.Moshu.Canvas2D.Sprite extends  Moshu.Canvas2D.CanvasImage
    constructor : (x,y,width,height, spriteRows, spriteCols, spriteWidth,spriteHeight,image) ->
        super(x,y,width,height,image)        
        @type = "image-sprite-2d"
        @spriteWidth = spriteWidth
        @spriteHeight = spriteHeight
        @spriteRows = spriteRows
        @spriteCols = spriteCols
        @spriteRowIndex = 0
        @spriteColIndex = 0       
    setCurrentSprite : (rowIndex, colIndex) ->
        @spriteRowIndex = rowIndex
        @spriteColIndex = colIndex        
    setSpriteRow : (rowIndex)->
        if rowIndex<@spriteRows and rowIndex>=0
            @spriteRowIndex = rowIndex        
    setSpriteCol : (colIndex)->
        if rowIndex<@spriteCols and colIndex>0
            @spriteColIndex = colIndex        
    setNextSpriteOnRow : ()->
        if @spriteColIndex<@spriteCols-1
            @spriteColIndex++
        else @spriteColIndex = 0         
    setPrevSpriteOnRow : ()->
        if @spriteColIndex>0
            @spriteColIndex--
        else @spriteColIndex = @spriteCols-1        
    #draw the image
    draw : () ->  
        ctx = @getContext()
        if ctx               
            ctx.drawImage(@image, 
            @spriteWidth * @spriteColIndex,@spriteHeight*@spriteRowIndex,@spriteWidth, @spriteHeight,
            @x, @y,@width, @height)        
                
# constructs an image
class window.Moshu.Canvas2D.CollisionManager
    constructor : () ->
    
    # returns the list of projection axes of a polygon shape
    getProjectionAxesForSAT : (shape) -> 
        axes = []   
        vertices = shape.vertices()     
        for v1,i in vertices            
            v2 = null
            if i is vertices.length-1
                v2 = vertices[0]
            else
                v2 = vertices[i+1]            
            
            vector1 = new Moshu.Canvas2D.Vector([v1[0],v1[1]])
            vector2 = new Moshu.Canvas2D.Vector([v2[0],v2[1]])
            
            vector1.substract(vector2.vector)
            vector1.perp()
            axes.push(vector1)        
        axes
    # returns the projection of a polygon shape on an axis
    getPolygonProjectionForSAT : (shape, axis) ->
        vertices = shape.vertices()
        min = axis.dotProduct(vertices[0])
        max = min        
        for v,i in vertices            
            val = axis.dotProduct([v.x, v.y])
            if min>val
                min = val
            if max<val
                max = val
        [min,max]
    checkCollideSAT : (shape1, shape2) ->
        #debugger;
        self = this
        axes1 = @getProjectionAxesForSAT(shape1)
        axes2 = @getProjectionAxesForSAT(shape2)            
        free = false
        # check for the first axis
        for axis in axes1                      
            p1 = self.getPolygonProjectionForSAT(shape1,axis)
            p2 = self.getPolygonProjectionForSAT(shape2,axis)                
            #check if p1 and p2 intersect
            overlap = false
            if (p1[0]>p2[0] and p1[0] < p2[1])
                overlap = true
            if (p2[0]>p1[0] and p2[0] < p1[1])
                overlap = true
            #if the projections do not overlap, the shapes do not intersect
            if not overlap
                free = true        
        if not free
            #chek for the second axis
            for axis in axes2                
                p1 = self.getPolygonProjectionForSAT(shape1,axis)
                p2 = self.getPolygonProjectionForSAT(shape2,axis)
                
                #check if p1 and p2 intersect
                overlap = false
                if (p1[0]>p2[0] and p1[0] < p2[1])
                    overlap = true
                if (p2[0]>p1[0] and p2[0] < p1[1])
                    overlap = true
                if not overlap
                    free = true            
        !free
    checkCollide : (shape1,shape2) ->        
        if shape1 and shape1.isPolygon and shape2 and shape2.isPolygon and shape1.vertices().length>1 and shape2.vertices().length>1
            collided = @checkCollideSAT(shape1,shape2)
            if collided
                collision = new Moshu.Canvas2D.Collision();
                collision.collider = shape1
                collision.collidee = shape2
                collision
            else
                null
        else 
            collision = new Moshu.Canvas2D.Collision();
            collision.collider = shape1
            collision.collidee = shape2
            collision